﻿#region Copyright (c) 2012-10, Olaf Kober <amarok.projects@gmail.com>
//================================================================================
//	Permission is hereby granted, free of charge, to any person obtaining a copy
//	of this software and associated documentation files (the "Software"), to deal
//	in the Software without restriction, including without limitation the rights
//	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//	copies of the Software, and to permit persons to whom the Software is
//	furnished to do so, subject to the following conditions:
//
//	The above copyright notice and this permission notice shall be included in
//	all copies or substantial portions of the Software.
//
//	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//	THE SOFTWARE.
//================================================================================
#endregion

using System;
using Amarok.Resources;


namespace Amarok
{
	/// <summary>
	/// This base class provides a light-weight and thread-safe implementation of the dispose pattern. Types 
	/// requiring support for <see cref="IDisposable"/> can derive from this base class to take advantage of 
	/// its implementation.
	/// </summary>
	public abstract class DisposableObject : IdentifiableObject,
		IDisposable
	{
		// helper
		private readonly Object mSyncDispose = new Object();

		// state
		private volatile Boolean mIsDisposed;


		#region ++ IDisposable Interface ++

		/// <summary>
		/// Performs application-defined tasks associated with freeing, releasing, or resetting resources.
		/// </summary>
		void IDisposable.Dispose()
		{
			this.DisposeObject();
		}

		#endregion

		#region ## Protected Interface ##

		/// <summary>
		/// Gets a boolean value indicating whether the object has been disposed.
		/// </summary>
		/// 
		/// <value>
		/// True, if the object is disposed; otherwise False.</value>
		protected Boolean IsDisposed
		{
			get
			{
				return mIsDisposed;
			}
		}


		/// <summary>
		/// Initializes a new instance. Sets the object-id to "type-name".
		/// </summary>
		protected DisposableObject()
		{
		}

		/// <summary>
		/// Initializes a new instance. Sets the object-id to "type-name[:instance-name]".
		/// </summary>
		/// 
		/// <param name="instanceName">
		/// The instance-name of this object. Might be null or an empty string.</param>
		protected DisposableObject(String instanceName)
			: base(instanceName)
		{
		}


		/// <summary>
		/// Throws an exception of type <see cref="ObjectDisposedException"/> if the object is already disposed.
		/// </summary>
		/// 
		/// <exception cref="ObjectDisposedException">
		/// A method or property was called on an already disposed object.</exception>
		protected void ThrowIfDisposed()
		{
			if (mIsDisposed)
			{
				throw new ObjectDisposedException(
					base.ObjectId,
					ExceptionResources.ObjectDisposed);
			}
		}

		/// <summary>
		/// Disposes the object, if it has not already been disposed before, and calls <see cref="OnDispose"/> 
		/// to allow sub-classes to perform custom dispose logic. This method can be called from sub-classes 
		/// to force disposal of the object.
		/// 
		/// This method will guarantee, even when called concurrently, that <see cref="OnDispose"/> is called 
		/// only once, meaning, that the object is disposed only once.
		/// </summary>
		protected void DisposeObject()
		{
			if (mIsDisposed)
				return;

			lock (mSyncDispose)
			{
				if (mIsDisposed)
					return;

				mIsDisposed = true;
			}

			// NOTE: we call custom dispose logic outside of the lock statement so that other threads 
			//       won't be blocked while running the dispose logic; instead all other threads will 
			//       just return immediately, as if the object has been disposed before.

			OnDispose();
		}

		#endregion

		#region ## Overrideable Methods ##

		/// <summary>
		/// An overrideable method that allows derived types to implement custom dispose logic.
		/// 
		/// This method is guaranteed to be called only once.
		/// 
		/// Although it is not recommended that overridden implementations throw exceptions, it should be 
		/// noted that any exception thrown by this method will be propagated back to the originator that 
		/// forces object disposal by calling either <see cref="IDisposable.Dispose"/> or 
		/// <see cref="DisposableObject.DisposeObject"/>.
		/// </summary>
		protected virtual void OnDispose()
		{
			// default implementation
		}

		#endregion

	}
}
